﻿// <copyright file="SchemaBuilder.cs" company="Oleg V. Polikarpotchkin">
// Copyright © 2009 Oleg V. Polikarpotchkin. All Right Reserved
// </copyright>
// <author>Oleg V. Polikarpotchkin</author>
// <email>ov-p@yandex.ru</email>
// <date>2009-04-12</date>
// <summary>Class to build point series schema from the mapping file.</summary>
// <revision>$Id$</revision>

using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;

namespace OpenWPFChart.Helpers.XmlItemDataReader
{
	/// <summary>
	/// Builds point series schema from the mapping file.
	/// </summary>
	/// <remarks>
	/// </remarks>
	static class SchemaBuilder
	{
		/// <summary>
		/// Creates the point series schema from the mapping file.
		/// </summary>
		/// <param name="mappings">Mapping collection.</param>
		/// <returns>Schema set containing the schema.</returns>
		public static XmlSchemaSet Build(List<Mapping> mappings)
		{
			//XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
			//namespaces.Add("", dataNamespaceName);
			//namespaces.Add("xs", "http://www.w3.org/2001/XMLSchema");
			XmlSchema schema = new XmlSchema()
			{
				TargetNamespace = Loader.dataNamespaceName,
				ElementFormDefault = XmlSchemaForm.Qualified,
				AttributeFormDefault = XmlSchemaForm.Unqualified,
				//Namespaces = namespaces
			};
			AddPointSetAttributes(schema);
			AddPointSeriesTypes(schema, mappings);
			AddRootElementType(schema, mappings);
			AddRootElement(schema);
			dump(schema);

			// Create an XmlSchemaSet and compile the schema.
			XmlSchemaSet schemaSet = new XmlSchemaSet();
			StringBuilder sbErrors = null;
			schemaSet.ValidationEventHandler += (sender, e) =>
			{
				if (sbErrors == null)
					sbErrors = new StringBuilder();
				sbErrors.AppendFormat("Validation error: {1}{0}Line={2}, position={3}{0}"
					, System.Environment.NewLine, e.Exception.Message
					, e.Exception.LineNumber, e.Exception.LinePosition);
			};
			schemaSet.Add(schema);
			schemaSet.Compile();
			if (sbErrors != null)
				// Validation error(s) occured.
				throw new XmlSchemaValidationException("Schema validation errors:"
					+ System.Environment.NewLine + sbErrors.ToString());

			return schemaSet;
		}

		/// <summary>
		/// Adds Point Series Type attributes to the <paramref name="schema"/>.
		/// </summary>
		/// <param name="schema">The schema.</param>
		/// <remarks>
		/// &lt;xs:attributeGroup name="pointSetAttributes"&gt;
		///		&lt;xs:attribute name="YName" type="xs:string" use="optional" /&gt;
		///		&lt;xs:attribute name="XName" type="xs:string" use="optional" /&gt;
		///	&lt;/xs:attributeGroup&gt;
		/// </remarks>
		static void AddPointSetAttributes(XmlSchema schema)
		{
			XmlSchemaAttributeGroup pointSetAttributes = new XmlSchemaAttributeGroup()
			{
				Name = "pointSetAttributes"
			};
			pointSetAttributes.Attributes.Add(new XmlSchemaAttribute()
			{
				Name = "XName",
				SchemaTypeName = new XmlQualifiedName("string", XmlSchema.Namespace),
				Use = XmlSchemaUse.Optional
			});
			pointSetAttributes.Attributes.Add(new XmlSchemaAttribute()
			{
				Name = "YName",
				SchemaTypeName = new XmlQualifiedName("string", XmlSchema.Namespace),
				Use = XmlSchemaUse.Optional
			});
			pointSetAttributes.Attributes.Add(new XmlSchemaAttribute()
			{
				Name = "ZName",
				SchemaTypeName = new XmlQualifiedName("string", XmlSchema.Namespace),
				Use = XmlSchemaUse.Optional
			});
			pointSetAttributes.Attributes.Add(new XmlSchemaAttribute()
			{
				Name = "ItemType",
				SchemaTypeName = new XmlQualifiedName("string", XmlSchema.Namespace),
				Use = XmlSchemaUse.Optional
			});
			schema.Items.Add(pointSetAttributes);
		}

		/// <summary>
		/// Adds the root element's type to the <paramref name="schema"/>.
		/// </summary>
		/// <param name="schema">The schema.</param>
		/// <param name="mappings">The mappings.</param>
		/// <remarks>
		/// <para>Root element's type repesents an unbounded sequence of Point Series elements.</para>
		/// <pre>
		/// &lt;xs:complexType name="itemsType"&gt;
		///    &lt;xs:choice maxOccurs="unbounded"&gt;
		///        &lt;xs:element name="Points.Int" type="pointsIntIntType"/&gt;
		///        &lt;xs:element name="Points.Int.Double" type="pointsIntDblType"/&gt;
		///        &lt;xs:element name="Points.Int.DateTime" type="pointsIntDttmType"/&gt;
		///        &lt;xs:element name="Points.Int.Date" type="pointsIntDtType"/&gt;
		///        ...
		///    &lt;/xs:choice&gt;
		/// &lt;/xs:complexType&gt;
		/// </pre>
		/// </remarks>
		static void AddRootElementType(XmlSchema schema, List<Mapping> mappings)
		{
			XmlSchemaChoice choice = new XmlSchemaChoice()
			{
				MaxOccursString = "unbounded"
			};
			AddRootElementTypeItems(choice, mappings);

			XmlSchemaComplexType rootElementType = new XmlSchemaComplexType()
			{
				Name = "rootElementType",
				Particle = choice
			};
			schema.Items.Add(rootElementType);
		}

		/// <summary>
		/// Populates the <paramref name="choice"/> with Point Series elements.
		/// </summary>
		/// <param name="choice">The choice element.</param>
		/// <param name="mappings">The mappings.</param>
		/// <remarks>
		/// <para>Root element's type repesents tan unbounded sequence of Point Series elements.</para>
		/// <pre>
		/// &lt;xs:complexType name="itemsType"&gt;
		///    &lt;xs:choice maxOccurs="unbounded"&gt;
		///        &lt;xs:element name="Points.Int" type="pointsIntIntType"/&gt;
		///        &lt;xs:element name="Points.Int.Double" type="pointsIntDblType"/&gt;
		///        &lt;xs:element name="Points.Int.DateTime" type="pointsIntDttmType"/&gt;
		///        &lt;xs:element name="Points.Int.Date" type="pointsIntDtType"/&gt;
		///        ...
		///    &lt;/xs:choice&gt;
		/// &lt;/xs:complexType&gt;
		/// </pre>
		/// </remarks>
		static void AddRootElementTypeItems(XmlSchemaChoice choice, List<Mapping> mappings)
		{
			foreach (Mapping map in mappings)
			{
				XmlSchemaElement element = new XmlSchemaElement()
				{
					Name = "Points." + map.DotSeparatedTypeString,
					SchemaTypeName = new XmlQualifiedName("points" + map.TypeString + "Type"
						, Loader.dataNamespaceName)
				};
				choice.Items.Add(element);
			}
		}

		/// <summary>
		/// Populates the <paramref name="schema"/> with point series types.
		/// </summary>
		/// <param name="schema">The schema.</param>
		/// <param name="mappings">The mappings.</param>
		/// <remarks>
		/// <para>Adds to the <paramref name="schema"/> the set of type definitions like follows:</para>
		/// <pre>
		/// &lt;xs:complexType name="pointsIntDoubleType"&gt;
		///  &lt;xs:sequence&gt;
		///    &lt;xs:element minOccurs="1" maxOccurs="unbounded" name="Point"&gt;
		///      &lt;xs:complexType&gt;
		///        &lt;xs:attribute name="x" type="xs:int" use="required" /&gt;
		///        &lt;xs:attribute name="y" type="xs:double" use="required" /&gt;
		///      &lt;/xs:complexType&gt;
		///    &lt;/xs:element&gt;
		///  &lt;/xs:sequence&gt;
		///  &lt;xs:attributeGroup ref="tns:pointSetAttributes" /&gt;
		/// &lt;/xs:complexType&gt;
		/// </pre>
		/// <para>Those type definitions are deduced from <paramref name="mappings"/>.</para>
		/// </remarks>
		static void AddPointSeriesTypes(XmlSchema schema, List<Mapping> mappings)
		{
			string[] attributeName = new string[] { "x", "y", "z" };
			foreach (Mapping map in mappings)
			{
				XmlSchemaComplexType elementType = new XmlSchemaComplexType();
				for (int i = 0; i < map.Axes.Length; i++)
				{
					XmlSchemaAttribute attr = new XmlSchemaAttribute()
					{
						Name = attributeName[i],
						SchemaTypeName = new XmlQualifiedName(map.Axes[i].XsdTypeString, XmlSchema.Namespace),
						Use = XmlSchemaUse.Required
					};
					elementType.Attributes.Add(attr);
				}

				XmlSchemaElement element = new XmlSchemaElement()
				{
					Name = "Point",
					MinOccurs = 1,
					MaxOccursString = "unbounded",
					SchemaType = elementType
				};

				XmlSchemaSequence sequence = new XmlSchemaSequence();
				sequence.Items.Add(element);

				XmlSchemaComplexType seriesType = new XmlSchemaComplexType()
				{
					Name = "points" + map.TypeString + "Type",
					Particle = sequence
				};
				seriesType.Attributes.Add(new XmlSchemaAttributeGroupRef()
				{
					RefName = new XmlQualifiedName("pointSetAttributes", Loader.dataNamespaceName)
				});

				schema.Items.Add(seriesType);
			}
		}

		/// <summary>
		/// Adds the root element to the <paramref name="schema"/>.
		/// </summary>
		/// <param name="schema">The schema.</param>
		/// <remarks>
		/// Root element: &lt;xs:element name="Items" type="rootElementType"/&gt;
		/// </remarks>
		static void AddRootElement(XmlSchema schema)
		{
			XmlSchemaElement rootElement = new XmlSchemaElement()
			{
				Name = "Items",
				SchemaTypeName = new XmlQualifiedName("rootElementType", Loader.dataNamespaceName)
			};
			schema.Items.Add(rootElement);
		}

		/// <summary>
		/// Dumps the schema.
		/// </summary>
		/// <param name="schema">The schema.</param>
		[Conditional("DEBUG")]
		static void dump(XmlSchema schema)
		{
			//XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable());
			//nsmgr.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema");
			//compiledSchema.Write(Console.Out, nsmgr);

			StringBuilder sb = new StringBuilder();
			StringWriter sw = new StringWriter(sb);
			schema.Write(sw);
			Debug.WriteLine(sb.ToString());
		}
	}
}
